Repository navigation
feat: virtual Amiibo scanning and the game's Amiibo rewards - #1521
Draft
nicholasgower wants to merge 7 commits into
Draft
nicholasgower wants to merge 7 commits into
nicholasgower wants to merge 7 commits into
Conversation
nicholasgower
force-pushed
the
feat/amiibo
branch
from
November 10, 2026 06:13
9245935 to
04d49a7
Compare
nicholasgower
marked this pull request as draft
November 10, 2026 07:36
Contributor
Author
|
Debugging and testing is ongoing. Once I've confirmed this functionality works, I will rebase and declare it ready for review. |
nicholasgower
force-pushed
the
feat/amiibo
branch
3 times, most recently
from
November 10, 2026 18:00
20b3d9b to
4f6ccdb
Compare
This was referenced Nov 11, 2026
Joey-Magnus
reviewed
Nov 11, 2026
Joey-Magnus
left a comment
Contributor
There was a problem hiding this comment.
Merged onto current main (15646d1), this is clean, and tests/run_all.py passes there (377 passed, 0 failed). The CI failures come from the old base: nx2/test_draw_parameters.py's mesh_fallback fixture, and the Windows nx2/test_nup_out_of_space.py path check, which fail the same way on other PRs from that period. A few points for when it comes out of draft:
- host/nn_nfp.c:150 reads HOS_AMIIBO_RANDOM_UID with atoi. Since #2042, switches use env_on from hookshot_env.h, so that
=trueand=onwork. - The random UID comes from rand(), and nothing in host/ calls srand, so every launch produces the same sequence of UIDs. That works against the once-a-day rule this switch is for: the first scan after a restart repeats a UID the game has already seen. Seeding once from hos_now_ns() would fix it.
- The default figure is Link (index 0), so every player gets a working amiibo with no reader as soon as this lands. That seems like a maintainer call; defaulting to None would keep today's behaviour.
This was referenced Nov 12, 2026
Draft
nicholasgower
force-pushed
the
feat/amiibo
branch
2 times, most recently
from
November 12, 2026 18:44
5dd4f52 to
54acbe9
Compare
The game's amiibo function asks Horizon for an NFC tag, but a PC has no reader, so every scan fell through to the generic stubs. This presents a virtual tag instead: the game sees a real Amiibo ID and runs its own reward code. - host/nn_nfp.c answers nn::nfp and nn::nfp::sdk at the SDK boundary: detection, mount, tag/model/common/register info, application areas and events. - host/nfp_amiibo.h holds the SDK layouts and the selectable figures: Link (Ocarina of Time, the default), Zelda, Young Link, Young Zelda, the two 2026 Ocarina of Time figures (Mask 101/102), and Mario for the unsupported path. - The Insert menu's Gameplay page has an Amiibo row; HOS_AMIIBO=<profile or 0x...>, HOS_AMIIBO_ID and HOS_AMIIBO_RANDOM_UID override it. - tests/test_amiibo.py checks the SDK offsets, IDs, numbering endianness, UIDs and effects. This has not been tested by a human: no in-game scan, reward, mask or unsupported-figure path has been run. The game-free check passes; the 2026 figures' retail IDs are not public, so their profiles use the series' next numbering and can be replaced with HOS_AMIIBO=0x.... Assisted-by: DeepSeek V4.1 Flash Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
The first human test showed the game's amiibo screen scanning the virtual tag but never applying the effect. Its runtime log had one unimplemented nfp import: nn::nfp::GetNpadId(unsigned int*, DeviceHandle const&) (EPj), because SDK 22's NpadIdType is a uint32_t typedef, not the enum name the older SDK mangled. The generic stub returned without writing the controller ID, so the game could not map the NFC device. Add that overload and its nn::nfp::sdk alias; the older enum name stays for other SDKs. Assisted-by: DeepSeek V4.1 Flash Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
The first fix still did not apply the reward, so log what the game actually receives: TagInfo, ModelInfo, RegisterInfo and CommonInfo once per distinct value, plus the device state, mount and application-area flow. The log is bounded (it only prints when the returned data changes) so a repeated scan does not flood it. Assisted-by: DeepSeek V4.1 Flash Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
The read log shows the game mounts the tag and reads the model, then resets detection without reading the register/common data. Log StartDetection, StopDetection, Unmount, GetState and the three event attaches, and signal the activate event on a late attach: the virtual tag is always present, so a UI that attaches after the worker's scan must not miss it. Assisted-by: DeepSeek V4.1 Flash Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
The whole tag was read inside StartDetection, so the game drew its 'touch the amiibo' prompt after the mount, model read and StopDetection had already finished. The tag now appears 300 ms after StartDetection, as a figure being tapped does, and StopDetection puts the reader back to Initialized so the game's next scan starts detection again. Also answer the GetApplicationArea/SetApplicationArea overloads the game imports: its SDK spells the size unsigned long (Pm/Em), not unsigned long long (Py/Ey), and the mangled names differ, so those calls reached the stub and got no size written. The Amiibo menu row gains a None entry (index 7) that presents no virtual tag, matching HOS_AMIIBO=none.
The header promised the reward, which is the game's decision and is not confirmed yet: the virtual tag only has to present the figure's data.
nicholasgower
force-pushed
the
feat/amiibo
branch
from
November 12, 2026 18:50
54acbe9 to
cd460e0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The game's amiibo function asks Horizon for an NFC tag, but a PC has no reader, so every scan currently falls through to the generic stubs. This adds a virtual tag, so the game sees a real Amiibo ID and runs its own reward code.
What changed
host/nn_nfp.canswersnn::nfpandnn::nfp::sdkat the SDK boundary: device listing, detection, mount/unmount, tag/model/common/register info, application areas, events and amiibo settings.host/nfp_amiibo.hholds the SDK layouts and the selectable figures: Link (Ocarina of Time, the default), Zelda, Young Link, Young Zelda, the two 2026 Ocarina of Time figures (Mask 101/102), and Mario for the unsupported path.HOS_AMIIBO=<profile or 0x...>,HOS_AMIIBO_IDandHOS_AMIIBO_RANDOM_UID=1override it;amiibo=is the saved key.StartDetectionrather than inside it, as a figure being tapped does. Presenting it inside the call made the whole mount-and-read finish before the game had drawn its "touch the amiibo" prompt.StopDetectionputs the reader back toInitialized, so the game's next scan starts detection again instead of waiting on a state it never leaves.GetApplicationArea/SetApplicationAreaare answered under both SDK spellings. The game imports theunsigned long(Pm/Em) form, and the mangled name differs fromunsigned long long(Py/Ey), so those calls were reaching the stub and no size was written.tests/test_amiibo.pychecks the SDK offsets, the ids, the numbering endianness, the UIDs and the effects.Testing
python -I -B tests/test_amiibo.pypasses, andpython -I -B tests/run_all.pypasses apart from two unrelated environment-only checks (Windows symlink privileges innx2/test_nup_paths.py, andwindows.hnot on the test's include path intests/test_precompile_compute_guard.py).GetTagInfo(UID, NFC-A/TYPE2) andGetModelInfo(e.g.character 010000, series 09, numbering 034bfor the Ocarina of Time Link figure), then stops detection. So the game receives the right tag and the right figure.HOS_AMIIBO=0x....No game files or build output are included.